package v2ray

import (
	"context"
	core "github.com/v2fly/v2ray-core/v5"
	"github.com/v2fly/v2ray-core/v5/app/dispatcher"
	"github.com/v2fly/v2ray-core/v5/app/log"
	"github.com/v2fly/v2ray-core/v5/app/proxyman"
	"github.com/v2fly/v2ray-core/v5/common"
	clog "github.com/v2fly/v2ray-core/v5/common/log"
	"github.com/v2fly/v2ray-core/v5/common/net"
	"github.com/v2fly/v2ray-core/v5/common/serial"
	"github.com/v2fly/v2ray-core/v5/common/session"
	"github.com/v2fly/v2ray-core/v5/features/outbound"
	_ "github.com/v2fly/v2ray-core/v5/proxy/http/simplified"
	"github.com/v2fly/v2ray-core/v5/proxy/trojan"
	"github.com/v2fly/v2ray-core/v5/transport"
	"github.com/v2fly/v2ray-core/v5/transport/pipe"
	"google.golang.org/protobuf/types/known/anypb"
	net2 "net"
	"net/http"
	"time"
)

type Dialer struct {
	Ins     *core.Instance
	Handler outbound.Handler
}

type ShadowSockClient struct {
	ClientConfig
	Method                         string `json:"method"`
	ExperimentReducedIvHeadEntropy bool   `json:"experimentReducedIvHeadEntropy"`
}

type ClientConfig struct {
	Address  string `json:"address"`
	Port     int    `json:"port"`
	Password string `json:"password"`
}

type VmessConfig struct {
	Vnext []*VmessVnext `json:"vnext"`
}

type VmessVnext struct {
	Address string       `json:"address"`
	Port    int          `json:"port"`
	Users   []*VmessUser `json:"users"`
}

type VmessUser struct {
	Id       string `json:"id"`
	AlterId  int    `json:"alterId"`
	Security string `json:"security"`
	Level    int    `json:"level"`
}

type VmessSample struct {
	Address string `json:"address"`
	Port    uint16 `json:"port"`
	Uuid    string `json:"uuid"`
}

func (d *Dialer) getLink(ctx context.Context) (*transport.Link, *transport.Link) {
	opt := pipe.OptionsFromContext(ctx)
	uplinkReader, uplinkWriter := pipe.New(opt...)
	downlinkReader, downlinkWriter := pipe.New(opt...)

	inboundLink := &transport.Link{
		Reader: downlinkReader,
		Writer: uplinkWriter,
	}

	outboundLink := &transport.Link{
		Reader: uplinkReader,
		Writer: downlinkWriter,
	}

	return inboundLink, outboundLink
}

func (d *Dialer) DialContext(ctx context.Context, network, address string) (net2.Conn, error) {
	if d.Handler == nil {
		return nil, http.ErrNotSupported
	}
	dest, err := net.ParseDestination(network + ":" + address)
	if err != nil {
		return nil, err
	}

	ob := &session.Outbound{
		Target: dest,
	}
	ctx = session.ContextWithOutbound(ctx, ob)
	in, out := d.getLink(ctx)

	content := session.ContentFromContext(ctx)
	if content == nil {
		content = new(session.Content)
		ctx = session.ContextWithContent(ctx, content)
	}

	go d.Handler.Dispatch(ctx, out)

	var readerOpt net.ConnectionOption
	if dest.Network == net.Network_TCP {
		readerOpt = net.ConnectionOutputMulti(in.Reader)
	} else {
		readerOpt = net.ConnectionOutputMultiUDP(in.Reader)
	}
	return net.NewConnection(net.ConnectionInputMulti(in.Writer), readerOpt), nil
}

func NewIns() (*core.Instance, error) {
	cfg := &core.Config{
		App: []*anypb.Any{
			serial.ToTypedMessage(&log.Config{
				Error: &log.LogSpecification{
					Type:  log.LogType_Console,
					Level: clog.Severity_Error,
				},
				Access: &log.LogSpecification{
					Type:  log.LogType_None,
					Level: clog.Severity_Error,
				},
			}),
			serial.ToTypedMessage(&dispatcher.Config{}),
			serial.ToTypedMessage(&proxyman.OutboundConfig{}),
			serial.ToTypedMessage(&proxyman.InboundConfig{}),
		},
	}

	return core.New(cfg)
}

func DoTest(transport *http.Transport) (bool, error) {

	cl := &http.Client{
		Transport: transport,
		Timeout:   10 * time.Second,
	}

	r, err := cl.Head("https://google.com")

	if r != nil {
		defer r.Body.Close()
	}
	if err != nil {
		return false, err
	}

	//b := strings.Builder{}
	//for s, v := range r.Header {
	//	b.WriteString(s)
	//	b.WriteString(":")
	//	for _, val := range v {
	//		b.WriteString(val)
	//		b.WriteString(" ")
	//	}
	//	b.WriteString("\r\n")
	//}
	return r.StatusCode == 200, nil
}

type Inbound struct {
	Protocol string `json:"protocol"`
	Port     int    `json:"port"`
	ListenOn string `json:"listen"`
}

type TrojanUser struct {
	Password string `json:"password"`
	Email    string `json:"email"`
	Level    string `json:"level"`
	trojan.ClientConfig
}

type HookOutHandler struct {
	outbound.Handler
}

func (h *HookOutHandler) Start() error {
	if h.Handler == nil {
		return nil
	}
	return h.Handler.Start()
}

func (h *HookOutHandler) Close() error {
	if h.Handler == nil {
		return nil
	}
	return h.Handler.Close()
}

func (h *HookOutHandler) Tag() string {
	if h.Handler == nil {
		return ""
	}
	return h.Handler.Tag()
}

func (h *HookOutHandler) Dispatch(ctx context.Context, link *transport.Link) {
	if h.Handler == nil {
		_ = common.Interrupt(link.Writer)
		_ = common.Interrupt(link.Reader)
		return
	}
	h.Handler.Dispatch(ctx, link)
}
